#!/bin/sh
#
# Based on server by Upper Stream (c) 2017, MIT license
# (https://gist.github.com/upperstream/b9b77429bc024541b7605aea76086ad8)

. start.sh
. get.sh
. password.sh

set -e

exec >>~/toymaker.log
exec 2>&1

program=${0##*/}

usage() {
	cat <<-EOF
	Usage:

	$program [-p port] [docroot]
	$program -h

	-p port : specify listening port number; defaults to 1313
	-h      : print this help and quit
	docroot : specify document root directory; defaults to the current directory
EOF
}

# default listening port
port=1313

nc=$(command -v nc) || true

pid_file=/tmp/$program.pid

slashes() {
	printf "%s" "$1" | sed 's|[^/]||g' | wc -m
}

field() {
	echo "$1" | cut -d '/' -f "$2"
}
makefifo() {
	name="$1"
	mkfifo -m 0600 "/tmp/$name.fifo" && echo "/tmp/$name.fifo"
}

templateMessage() {
	message="$1"
	templateName=$2
	contentTypeHeader=$3
	while [ -n "$contentTypeHeader" ]
	do
		contentType=$(echo "$contentTypeHeader" | cut -d ',' -f1)
		contentTypeHeader=$(echo "$contentTypeHeader" | cut -d ',' -f2-)
		contentType=$(echo "$contentType" | cut -d ';' -f1)
		case $contentType in
		text/plain)
			ext='txt'
			;;
		text/html|\*|\*/\*|'')
			ext='html'
			;;
		image/\*|image/svg)
			ext='svg'
			;;
		*)
			continue
			;;
		esac
		[ -f "templates/$templateName.$ext" ] && break
		ext=''
	done
	[ -n "$ext" ] || return 1
	#shellcheck disable=SC1090
	. "templates/$templateName.$ext"
	printf '%s\n' "$contentType"
	template "$message"
}

respond200() {
	page="$1"
	contentType="$2"
	[ -z "$contentType" ] && contentType='text/html'

	printf "HTTP/1.1 200 OK\r\nContent-Length: %s\r\nContent-Type: %s\r\n\r\n%s\r\n" "${#page}" "$contentType" "$page" > "$fifo1"

	printf "200 %s" "${#page}"
}

respond200file() {
	filePath="$1"
	contentType="$2"

	{ cat "$fifo2" > /dev/null; printf ""; } > "$fifo1" &

	printf "HTTP/1.1 200 OK\r\nContent-Length: %s\r\nContent-Type: %s\r\n\r\n" "$(wc -c "$filePath" | cut -d ' ' -f1)"  "$contentType" >> "$fifo1"

	cat "$filePath" >> "$fifo1"

	sleep 1; echo "end" > "$fifo2"
	printf "200 %s" "$(wc -c "$filePath" | cut -d ' ' -f1)"
}

respond202() {
	toy=$1
	item=$2
	message=$3
	contentType="$4"
	[ -z "$contentType" ] && contentType='text/html'
	printf "HTTP/1.1 202 Accepted\r\nContent-Length: %s\r\nLocation: /toys/%s/%s\r\n\r\n%s\r\n" "${#message}" "$toy" "$item" "$message" > "$fifo1"
	printf "202 -"
}

respond401() {
	message=""
	printf "HTTP/1.1 401 Unauthorized\r\nContent-Length: %s\r\n\r\n%s\r\n" "${#message}" "$message" > "$fifo1"
	printf "401 -"
}

respond403() {
	message=""
	printf "HTTP/1.1 403 Forbidden\r\nContent-Length: %s\r\n\r\n%s\r\n" "${#message}" "$message" > "$fifo1"
	printf "403 -"
}

respond404() {
	path=$1
	message=$(templateMessage "$path not found" 'message' 'text/html' | tail -n+2)
	printf "HTTP/1.1 404 Not Found\r\nContent-Length: %s\r\n\r\n%s\r\n" "${#message}" "$message" > "$fifo1"
	printf "404 -"
}

respond405() {
	method=$1
	message=$(templateMessage "$method unsupported" 'message' 'text/html' | tail -n+2)
	printf "HTTP/1.1 405 Method Not Allowed\r\nContent-Length: %s\r\n\r\n%s\r\n" "${#message}" "$message" > "$fifo1"
	printf "405 -"
}

respond406() {
	contentType=$1
	message="$contentType unsupported"
	printf "HTTP/1.1 406 Not Acceptable\r\nContent-Length: %s\r\n\r\n%s\r\n" "${#message}" "$message" > "$fifo1"
	printf "406 -"
}

respond500() {
	message=$(templateMessage "Server error: $1" 'message' 'text/html' | tail -n+2)
	printf "HTTP/1.1 500 Internal Server Error\r\nContent-Length: %s\r\n\r\n%s\r\n" "${#message}" "$message" > "$fifo1"
	printf "500 -"
}

check_auth() {
	authorization=$1
	stored=$(cat password)
	if [ -z "$stored" ] || [ -z "$authorization" ]
	then
		respond401
		return 1
	fi
	if ! printf '%s' "$authorization" | verify "$stored"
	then
		respond403
		return 1
	fi
}

execute() {
	method="$1"
	path="$2"
	authorization="$3"
	accept="$4"
	_toys=$(field "$path" 2)
	if [ "$_toys" != 'toys' ]
	then
		respond404 "$path"
		return 1
	fi

	case $method in
	GET)
		if [ "$(slashes "$path")" -eq 2 ]
		then
			toy=$(field "$path" 3)
			if [ "$toy" = '' ]
			then
				if page=$(templateMessage "$(list_toys)" 'toys' 'text/html')
				then
					respond200 "$(echo "$page" | tail -n+2)"
				else
					respond406 "$accept"
					return 1
				fi
			else
				r=$(list_items "$toy")
				status=$(echo "$r" | cut -d ';' -f1)
				content=$(echo "$r" | cut -d ';' -f2)
				if [ "$status" = 'n' ]
				then
					respond404 "toy $toy"
					return 1
				elif page=$(templateMessage "$toy;$content" 'toy' 'text/html')
				then
					respond200 "$(echo "$page" | tail -n+2)"
				else
					respond406 "$accept"
					return 1
				fi
			fi
		elif [ "$(slashes "$path")" -eq 3 ]
		then
			toy=$(field "$path" 3)
			item=$(field "$path" 4)
			if [ -z "$item" ]
			then
				respond404 "$path"
				return 1
			fi
			r=$(show_item "$toy" "$item")
			artifacts=$(item_artifacts "$toy" "$item")
			status=$(echo "$r" | head -n1 | cut -d ';' -f1)
			if [ "$status" = 'n' ]
			then
				respond404 "item $toy/$item"
				return 1
			elif page=$(templateMessage "$artifacts;$toy;$item;$r" 'item' "$accept")
			then
				body=$(echo "$page" | tail -n+2)
				contentType=$(echo "$page" | head -n1)
				if [ "$status" = 'r' ]
				then
					respond202 "$toy" "$item" "$body" "$contentType"
				else
					respond200 "$body" "$contentType"
				fi
			else
				respond406 "$accept"
				return 1
			fi
		elif [ "$(slashes "$path")" -eq 4 ]
		then
			toy=$(field "$path" 3)
			item=$(field "$path" 4)
			artifactName=$(field "$path" 5)
			r=$(get_artifact "$toy" "$item" "$artifactName")
			status=$(echo "$r" | cut -d ';' -f 1)
			if [ "$status" = 'n' ]
			then
				respond404 "artifact $toy/$item/$artifactName"
				return 1
			else
				contentType=$(echo "$r" | cut -d ';' -f 2)
				filePath=$(echo "$r" | cut -d ';' -f 3)
				respond200file "$filePath" "$contentType"
			fi
		else
			respond404 "$path"
			return 1
		fi
		;;
	POST)
		check_auth "$authorization" || return 1

		toy=$(field "$path" 3)
		if [ "$(slashes "$path")" -ne 3 ] || [ -n "$(field "$path" 4)" ]
		then
			respond404 "$path"
			return 1
		fi

		if ! (podman image ls --format "{{.Repository}}" | grep -x "localhost/toy_$toy" -q)
		then
			respond404 "toy $toy"
			return 1
		fi

		if item=$(start_item "$toy")
		then
			respond202 "$toy" "$item"
		else
			respond500 "error while creating $toy"
			return 1
		fi

		;;
	DELETE)
		check_auth "$authorization" || return 1

		if [ "$(slashes "$path")" -eq 3 ]
		then
			toy=$(field "$path" 3)
			item=$(field "$path" 4)
			if [ -z "$toy" ] || [ -z "$item" ]
			then
				respond404 "$path"
				return 1
			fi

			if stop_item "$toy" "$item"
			then
				respond200 ""
			else
				respond500 "error while stopping $toy/$item"
				return 1
			fi
		else
			respond404 "$path"
			return 1
		fi
		;;
	*)
		respond405 "$method"
		return 1
		;;
	esac
}

cleanup() {
	rm "$fifo1" "$fifo2" "$pid_file"
}

interrupt() {
	cleanup
	exit 0
}

parse() {
	cr=$(printf "\r")
	while read -r line; do
		line="${line%"$cr"}"
		case "$line" in
		GET*|POST*|PUT*|DELETE*|HEAD*|OPTIONS*|TRACE*)
			# shellcheck disable=SC2086
			set -- $line
			method=$1
			path=$2
			;;
		Host:*)
			host=${line#Host: }
			;;
		Accept:*)
			accept=${line#Accept: }
			;;
		accept:*)
			accept=${line#accept: }
			;;
		Authorization:*)
			authorization=${line#Authorization: }
			;;
		authorization:*)
			authorization=${line#authorization: }
			;;
		*:*)
			;;
		"")
			date=$(date +'%d/%m/%Y:%H:%M:%S %z')
			status_length=$(execute "$method" "$path" "$authorization" "$accept")
			printf "%s %s \"http://%s%s\" %s\n" "$date" "$method" "$host" "$path" "$status_length"
			trap - INT
			exit
			;;
		*)
			;;
		esac
	done
}

# dependency verification
for cmd in nc podman awk
do
	command -v $cmd >/dev/null || { echo "$0: \`$cmd\` not found"; exit 2; }
done

while getopts p:h opt; do
	case $opt in
	p) port=$OPTARG;;
	h) usage; exit 255;;
	*) usage; exit 255;;
	esac
done
shift $((OPTIND-1))

if [ $# -eq 0 ]; then
	docroot=.
else
	docroot="$1"
fi
docroot=$(cd "$docroot"; pwd)

# FIFO to write HTTP response
if ! fifo1=$(makefifo "${0##*/}.$$.1"); then
	echo "$0: creating a named pipe failed. Already running?" 1>&2
	exit 1
fi

# FIFO for internal event notification
if ! fifo2=$(makefifo "${0##*/}.$$.2"); then
	echo "$0: creating a named pipe failed. Already running?" 1>&2
	exit 1
fi

trap interrupt INT

echo $$ > "$pid_file"

cat 1>&2 <<EOF
Simple HTTP Server

Listening at the port number $port.
Type ^C to quit.
EOF

# shellcheck disable=SC2002
while cat "$fifo1" | "$nc" -l -p "$port" | parse; do
	:
done
cleanup
exit
